package model;

import static org.lwjgl.opengl.GL11.GL_COLOR_ARRAY;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_NORMAL_ARRAY;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
import static org.lwjgl.opengl.GL11.glColorPointer;
import static org.lwjgl.opengl.GL11.glDisableClientState;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import static org.lwjgl.opengl.GL11.glEnableClientState;
import static org.lwjgl.opengl.GL11.glNormalPointer;
import static org.lwjgl.opengl.GL11.glVertexPointer;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
import static org.lwjgl.opengl.GL15.glGenBuffers;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.lwjgl.BufferUtils;
import org.lwjgl.util.vector.Vector3f;

/**
 * 
 * @author Jevgeni.Krutov
 * @author Sri Harsha Chilakapati
 *
 */
public class Model {
	List<Vector3f> vertices;
	List<Vector3f> normals;
	List<Face> faces;
	HashMap<String, Material> materials;
	BoundingBox boundingBox;
	
	Model() {
		vertices = new ArrayList<Vector3f>();
		normals = new ArrayList<Vector3f>();
		faces = new ArrayList<Face>();
		materials = new HashMap<String, Material>();
	}
	
	void calculateBB() {
		float minX = 1000;
		float minY = 1000;
		float minZ = 1000;
		float maxX = -1000;
		float maxY = -1000;
		float maxZ = -1000;
		for(Vector3f v : vertices) {
			if(v.x < minX)
				minX = v.x;
			if(v.y < minY)
				minY = v.y;
			if(v.z < minZ)
				minZ = v.z;
				
			if(v.x > maxX)
				maxX = v.x;
			if(v.y > maxY)
				maxY = v.y;
			if(v.z > maxZ)
				maxZ = v.z;
		}
		boundingBox = new BoundingBox(minX, maxX, minY, maxY, minZ, maxZ);
//		System.out.println(boundingBox);
	}
	
	public BoundingBox getBoundingBox() {
		return boundingBox;
	}
	
	
	
	
	
	
	
	
	
	
	// VBO handles
	int vboVertexID;
	int vboNormalID;
	int vboColorID;


	/**
	 * Prepares a VBO for this model to render
	 */
	/*private*/ void prepareVBO() {
		// Create the handles
		vboNormalID = glGenBuffers();
		vboVertexID = glGenBuffers();
		vboColorID = glGenBuffers();

		// Create the buffers
		FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(9 * faces.size());
		FloatBuffer normalBuffer = BufferUtils.createFloatBuffer(9 * faces.size());
		FloatBuffer colorBuffer = BufferUtils.createFloatBuffer(9 * faces.size());

		// Iterate over each face in the model and add them to the VBO
		for (Face face : faces) {
			// Retrieve the material of the face
			Material material = face.getMaterial();

			// Get the first vertex of the face
			Vector3f v1 = vertices.get((int) face.getVertex().x - 1);
			vertexBuffer.put(v1.x).put(v1.y).put(v1.z);
			// Get the color of the vertex
			colorBuffer.put(material.getDiffuse().x).put(material.getDiffuse().y).put(material.getDiffuse().z);

			// Get the second vertex of the face
			Vector3f v2 = vertices.get((int) face.getVertex().y - 1);
			vertexBuffer.put(v2.x).put(v2.y).put(v2.z);
			// Get the color of the face
			colorBuffer.put(material.getDiffuse().x).put(material.getDiffuse().y).put(material.getDiffuse().z);

			// Get the third vertex of the face
			Vector3f v3 = vertices.get((int) face.getVertex().z - 1);
			vertexBuffer.put(v3.x).put(v3.y).put(v3.z);
			// Get the color of the face
			colorBuffer.put(material.getDiffuse().x).put(material.getDiffuse().y).put(material.getDiffuse().z);

			// Get the first normal of the face
			Vector3f n1 = normals.get((int) face.getNormal().x - 1);
			normalBuffer.put(n1.x).put(n1.y).put(n1.z);

			// Get the second normal of the face
			Vector3f n2 = normals.get((int) face.getNormal().y - 1);
			normalBuffer.put(n2.x).put(n2.y).put(n2.z);

			// Get the third normal of the face
			Vector3f n3 = normals.get((int) face.getNormal().z - 1);
			normalBuffer.put(n3.x).put(n3.y).put(n3.z);
		}

		// Rewind the buffers
		vertexBuffer.rewind();
		normalBuffer.rewind();
		colorBuffer.rewind();

		// Create the vertex VBO
		glBindBuffer(GL_ARRAY_BUFFER, vboVertexID);
		glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		// Create the normal VBO
		glBindBuffer(GL_ARRAY_BUFFER, vboNormalID);
		glBufferData(GL_ARRAY_BUFFER, normalBuffer, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		// Create the color VBO
		glBindBuffer(GL_ARRAY_BUFFER, vboColorID);
		glBufferData(GL_ARRAY_BUFFER, colorBuffer, GL_STATIC_DRAW);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
	}

	/**
	 * Renders this model on the screen
	 */
	public void render() {
		// Enable client states
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);

		// Bind the normal buffer
		glBindBuffer(GL_ARRAY_BUFFER, vboNormalID);
		glNormalPointer(GL_FLOAT, 0, 0);

		// Bind the color buffer
		glBindBuffer(GL_ARRAY_BUFFER, vboColorID);
		glColorPointer(3, GL_FLOAT, 0, 0);

		// Bind the vertex buffer
		glBindBuffer(GL_ARRAY_BUFFER, vboVertexID);
		glVertexPointer(3, GL_FLOAT, 0, 0);

		// Draw all the faces as triangles
		glDrawArrays(GL_TRIANGLES, 0, 9 * faces.size());

		// Disable client states
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
	}

	public void dispose() {
		glDeleteBuffers(vboVertexID);
		glDeleteBuffers(vboNormalID);
		glDeleteBuffers(vboColorID);
	}

	public List<Vector3f> getVertices() {
		return vertices;
	}

	public List<Vector3f> getNormals() {
		return normals;
	}

	public List<Face> getFaces() {
		return faces;
	}

	public HashMap<String, Material> getMaterials() {
		return materials;
	}
}
